/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			OpengGLRes.d
	Description:	opengl resource stuff, separated from main opengl renderer module
	Date:			25.11.2007 by Digited
*/
module moonglide.renderer.opengl.OpenGLRes;

import
	moonglide.util.MoonUtil,
	moonglide.core.Events,

	moonglide.renderer.IRendererRes,

	moonglide.renderer.opengl.GL,
	moonglide.renderer.opengl.OpenGLRenderer,
	moonglide.renderer.opengl.OpenGLShaderWork;

//================================================================
/// Renderer & Resources
//================================================================
class COpenGLRendererRes : IRendererRes
{
	mixin TMoonGlideObject;

	protected:
		COpenGLCaps		    _caps;
		COpenGLShader	    _shaderMan;


//---shader-----------------------

		bool _upLoadShader( CResShaderComposite sh )
        in
		{
			moonAssert( sh !is null, "sh is null", __FILE__, __LINE__ );
		}
		body
		{
			if( !sh.enabled )
				return true;

			bool res = true;

			if( sh.loaded )
			{
				debug debugLog.trace( "Trying to load an already loaded shader: " ~ sh.id );
				return true;
			}

			if( _shaderMan.AnalyzeShader(sh) )
				if( !_shaderMan.LoadShader(sh) )
				{
					res = false;
					debug debugLog.trace( "Shader is not active: " ~ sh.id );
				}

			if( res )
				if( _shaderMan.LinkShader( sh ) )
					sh.setLoaded;
				else
					res = false;

			if( !res )
				sh.setNotLoaded();

			return res;
		}

//---------------------------------

		bool _unLoadShader( CResShaderComposite sh )
		in
		{
			moonAssert( sh !is null, "sh is null", __FILE__, __LINE__ );
		}
		body
		{
			if( !sh.enabled )
				return true;

			_shaderMan.UnloadShader(sh);
			sh.setNotLoaded;
			return true;
		}

//---mesh-------------------------

		void _upLoadMesh( CResMesh mesh )
		in
		{
			moonAssert( mesh !is null, "mesh is null", __FILE__, __LINE__ );
		}
		body
		{
			bool vbo_loaded = _caps.useVBO;

            if( !vbo_loaded )
            {
                mesh.setLoaded();
                return;
            }

			if( mesh.loaded )
			{
	//		    debug debugLog.trace(mesh.id~" : update mesh");
                void updateField(CResMeshField Field)
                {
                    if(( !Field.needToUpdate ) || ( Field.isStatic ))
                        return;

                    uint para = Field.countData == 1 ? GL_ELEMENT_ARRAY_BUFFER_ARB : GL_ARRAY_BUFFER_ARB;

                    glBindBufferARB( para, Field.vboBuff );
                    glBufferDataARB( para , Field.GetLength()*Field.countData*4, Field.GetData(), GL_STREAM_DRAW_ARB );

                    //		glBufferDataARB( para , Field.GetLength()*Field.countData*4, null, GL_STREAM_DRAW_ARB );
                    //		glMapBufferARB( para,	GL_WRITE_ONLY);
                    //		glBufferSubDataARB( para , 0, Field.GetLength()*Field.countData*4, Field.GetData() );
                    //		glUnmapBufferARB( para);
                    /// What is faster??
                    //		glBufferSubDataARB( para , 0, Field.GetLength()*Field.countData*4, Field.GetData() );
                }


                if( _caps.useMultiTex )
                {
                    glClientActiveTextureARB(GL_TEXTURE1_ARB);
                    updateField(mesh.fields["Tangent"]);

                    glClientActiveTextureARB(GL_TEXTURE2_ARB);
                    updateField(mesh.fields["BiNormal"]);

                    glClientActiveTextureARB(GL_TEXTURE0_ARB);
                }

                glEnableClientState( GL_TEXTURE_COORD_ARRAY );
                updateField(mesh.fields["TexCoord"]);

                glEnableClientState( GL_NORMAL_ARRAY );
                updateField(mesh.fields["Normal"]);

                glEnableClientState( GL_COLOR_ARRAY );
                updateField(mesh.fields["Color"]);

                updateField(mesh.fields["Index"]);
                updateField(mesh.fields["Vertex"]);

			} else
			{
			    debug debugLog.trace(mesh.id~" : create mesh");
                void CreateField(inout CResMeshField field)
                {
                    uint Mode = (field.isStatic) ? GL_STATIC_DRAW_ARB : GL_STREAM_DRAW_ARB ;
                    glGenBuffersARB( 1, &field.vboBuff );
                    uint para = field.countData == 1 ? GL_ELEMENT_ARRAY_BUFFER_ARB : GL_ARRAY_BUFFER_ARB;

                    if( field.GetLength()!= 0 )
                    {
                        glBindBufferARB( para , field.vboBuff );
                        glBufferDataARB( para , field.GetLength()*field.countData*4, field.GetData(), Mode );
                    }
                }

                CreateField( mesh.fields["Vertex"] );
                CreateField( mesh.fields["Normal"] );
                CreateField( mesh.fields["Color"] );
                CreateField( mesh.fields["TexCoord"] );
                CreateField( mesh.fields["BiNormal"] );
                CreateField( mesh.fields["Tangent"] );
                CreateField( mesh.fields["Index"] );

                mesh.setLoaded;
			}
		}

//---------------------------------

		void _unLoadMesh( CResMesh mesh )
		in
		{
			moonAssert( mesh !is null, "mesh is null", __FILE__, __LINE__ );
		}
		body
		{
			if( mesh.loaded )
			{
				if( _caps.useVBO )
					foreach (inout field; mesh.fields)
						glDeleteBuffersARB( 1, &field.vboBuff );

				mesh.setNotLoaded();
			}
		}

//---material---------------------

		void _upLoadMaterial( CResMaterial mat )
		in
		{
			moonAssert( mat !is null, "mat is null", __FILE__, __LINE__ );
		}
		body
		{
			debug debugLog.trace( "loading material: " ~ mat.id );

			foreach( tex; mat.textures )
			{
				debug debugLog.trace( "load texture: " ~ tex.id );
				upLoadTexture( tex );
			}

			if( mat.shader !is null )
				_upLoadShader( mat.shader );

			mat.setLoaded;
		}

//---------------------------------

		void _unLoadMaterial( CResMaterial mat )
		in
		{
			moonAssert( mat !is null, "mat is null", __FILE__, __LINE__ );
		}
		body
		{
			debug debugLog.trace( "unloading material: " ~ mat.id );

			if( mat.loaded() )
			{
				foreach( inout tex; mat.textures )
				{
					if( tex is null )
					{
						debug debugLog.trace( "tex of " ~ mat.id ~ " is null" );
						continue;
					}

					debug debugLog.trace( "unloading texture: " ~ tex.id );
					unLoadTexture( tex );
				}

				if( mat.shader !is null )
					_unLoadShader( mat.shader );

				mat.setNotLoaded;
			}
		}

	public:
		void upLoadRes( IResource res )
        in
		{
			moonAssert( res !is null, "res is null", __FILE__, __LINE__ );
		}
		body
		{
			if( ( res.classinfo is CResTextureData.classinfo ) || ( res.classinfo is CResShader.classinfo ) )
				return;


			if( res.classinfo is CResMaterial.classinfo )
				_upLoadMaterial( cast(CResMaterial) res );
			else
				if( res.classinfo is CResMesh.classinfo )
					_upLoadMesh( cast(CResMesh) res );
				    else
                        moonAssert( 0, "Unknown loading resource type", __FILE__, __LINE__ );
		}


		void unLoadRes( IResource res )
		in
		{
			moonAssert( res !is null, "res is null", __FILE__, __LINE__ );
		}
		body
		{
			if ( !res.loaded )
			{
				debug debugLog.trace( "Trying to unload a not loaded resource: " ~ res.id );
				return;
			}

			if( ( res.classinfo is CResTextureData.classinfo ) || ( res.classinfo is CResShader.classinfo ) )
				return;

			if( res.classinfo is CResMaterial.classinfo )
				_unLoadMaterial( cast(CResMaterial) res );
			else
				if( res.classinfo is CResMesh.classinfo )
					_unLoadMesh( cast(CResMesh) res );
				    else
                        moonAssert( 0, "Unknown loading resource type", __FILE__, __LINE__ );
		}
	/*
		void onEventCallback( CEvent event, IEventGenerator gen )
		{
			switch( event.type )
			{
				case eeventtype.resource:
					switch( event.subtype )
					{
						case eresourceevent.created:
							break;

						case eresourceevent.destroyed:
							if( cast(IResource)gen !is null )
								UnloadRes( cast(IResource)gen );
							break;

						case eresourceevent.load:
							if( cast(IResource)gen !is null )
								UploadRes( cast(IResource)gen );
							break;

						case eresourceevent.unload:
							if( cast(IResource)gen )
								UnloadRes( cast(IResource)gen );
							break;

						default: break;
					}
				break;

				default: break;
			}
		}

		void onEventReceive( CEvent event, IEventSender sender )
		{
			//
		}
	*/

		IShaderManager shaderMan() { return _shaderMan; }

		this( COpenGLCaps caps, COpenGLShader sh )
		{
			_id = "openglres";
			_caps = caps;
			_shaderMan = sh;
        }



		//---texture----------------------
		void upLoadTexture( CResTexture tex )
		in
		{
			moonAssert( tex !is null, "tex is null", __FILE__, __LINE__ );
		}
		body
		{
			if( !tex.loaded )
			{
                void DetectType()
                {
                    switch( tex.textureType )
                    {
                        case Tex_1d: 	tex.tType = GL_TEXTURE_1D;	break;
                        case Tex_2d: 	tex.tType = GL_TEXTURE_2D;	break;
                        case Tex_3d: 	tex.tType = GL_TEXTURE_3D;	break;
                        case Tex_Cube:	tex.tType = GL_TEXTURE_CUBE_MAP_ARB;
                            debug debugLog.trace( "cubemap: " ~ tex.id );	break;
                    }
                }
                DetectType();

                glEnable ( tex.tType );
                // Generate OpenGL texture index
                glGenTextures( 1, &tex.textureID );
			}
    ///        if ( !tex.needToUpdate )
    ///            return;

            glEnable ( tex.tType );

GetGLError(__FILE__,__LINE__);
            glBindTexture( tex.tType, tex.textureID );

			foreach( inout texdata; tex.data )
			if( texdata )
	///		if( texdata.needToUpdate )
			{
			    /// ///////////////
                void DetectSide()
                {
                    if( tex.textureType == Tex_Cube )
                        switch( texdata.texType )
                        {
                            case ETexTarget.Tex_Cube_X_Pos: 	texdata.gtexType = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;
                                debug debugLog.trace( "X+: " ~ texdata.id );	break;
                            case ETexTarget.Tex_Cube_X_Neg: 	texdata.gtexType = GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB;
                                debug debugLog.trace( "X-: " ~ texdata.id );	break;
                            case ETexTarget.Tex_Cube_Y_Pos: 	texdata.gtexType = GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB;
                                debug debugLog.trace( "Y+: " ~ texdata.id );	break;
                            case ETexTarget.Tex_Cube_Y_Neg: 	texdata.gtexType = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB;
                                debug debugLog.trace( "Y-: " ~ texdata.id );	break;
                            case ETexTarget.Tex_Cube_Z_Pos: 	texdata.gtexType = GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB;
                                debug debugLog.trace( "Z+: " ~ texdata.id );	break;
                            case ETexTarget.Tex_Cube_Z_Neg: 	texdata.gtexType = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB;
                                debug debugLog.trace( "Z-: " ~ texdata.id );	break;
                            default:
                                moonAssert( 0, "default switch called in DetectSide for " ~ texdata.id, __FILE__, __LINE__ );
                                break;
                        }
                    else
                        texdata.gtexType = tex.tType;
                }
			    /// ///////////////
				DetectSide();


                //debug debugLog.trace( Integer.toString(texdata.width) );
                //debug debugLog.trace( Integer.toString(texdata.height) );

				uint gltype,numC;
                numC = texdata.numComponents;
				switch( texdata.format )
				{
					case IL_MOON_DEPTH24:
					{
					     gltype =  GL_DEPTH_COMPONENT;
					     numC = GL_DEPTH_COMPONENT24;
					     texdata.filter = ETextureFilter.tfBilinear;
					     texdata.useMipMap = false;
					     texdata.clampToEdge = true;
					     break;
					}

					case IL_BGR: gltype = GL_BGR;	 break;
					case IL_BGRA: gltype = GL_BGRA;	 break;
					case IL_RGB: gltype = GL_RGB;	 break;
					case IL_RGBA: gltype = GL_RGBA;  break;
					case IL_LUMINANCE: gltype = GL_LUMINANCE; break;
					default:
						moonAssert( 0, "default switch called in COpenGLRendererBind.UpdateTexture for " ~ texdata.id, __FILE__, __LINE__ );
						break;
				}

                switch( texdata.filter )
				{
					case ETextureFilter.tfAnisotropy:
						if( EXTTextureFilterAnisotropic.isEnabled() )
						{
							if( texdata.filterLevel > _caps.maxAnisotropyFilter ) texdata.filterLevel = _caps.maxAnisotropyFilter;
							glTexParameteri( tex.tType,GL_TEXTURE_MAX_ANISOTROPY_EXT, texdata.filterLevel );
						}
					case ETextureFilter.tfTrilinear:
						glTexParameterf( tex.tType, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
						glTexParameterf( tex.tType, GL_TEXTURE_MIN_FILTER, (texdata.useMipMap) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR );
						break;
					case ETextureFilter.tfBilinear:
						glTexParameterf( tex.tType, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
						glTexParameterf( tex.tType, GL_TEXTURE_MIN_FILTER, (texdata.useMipMap) ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST );
						break;
					default:
						moonAssert( 0, "default switch called in OpenGLRendererBind.BindTexture for " ~ texdata.id, __FILE__, __LINE__ );
						break;
				}
GetGLError(__FILE__,__LINE__);
                if( texdata.clampToEdge )
                {
                    glTexParameteri ( tex.tType,  GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                    glTexParameteri ( tex.tType,  GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                  }

                if( texdata.format == IL_MOON_DEPTH24 )
                {
                    glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
//                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
                } else
                {
  //                  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
    //                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
                }


				if( SGISGenerateMipMap.isEnabled() )
				{
					glTexParameteri( tex.tType, GL_GENERATE_MIPMAP_SGIS, (texdata.useMipMap) ? GL_TRUE : GL_FALSE );

					glTexImage2D( texdata.gtexType, 0, numC, texdata.width, texdata.height,
								  0, gltype, GL_UNSIGNED_BYTE, texdata.GetData() );
				}
				else
				if( texdata.useMipMap )
					gluBuild2DMipmaps(
						texdata.gtexType,
						numC,
						texdata.width,
						texdata.height,
						gltype,
						GL_UNSIGNED_BYTE,
						texdata.GetData() );
				else
					glTexImage2D( texdata.gtexType, 0, numC, texdata.width, texdata.height,
								  0, gltype, GL_UNSIGNED_BYTE, texdata.GetData() );

            	debug debugLog.trace( "texdata " ~ texdata.id );
            	debug debugLog.trace( "numComponents : " ~ intToStr(numC) ~ "  GL_DEPTH_COMPONENT24 =" ~intToStr(GL_DEPTH_COMPONENT24) ~ "  " ~ intToStr(GL_DEPTH_COMPONENT) );
            	debug debugLog.trace( "size : " ~ intToStr(texdata.width) ~ " x "~ intToStr(texdata.height) );
            	debug char[] tmp = ( texdata.GetData() is null ) ? "null" : "not null";
            	debug debugLog.trace( "data : " ~ tmp);
            	debug debugLog.trace( "loaded/reloaded to device" );


				texdata.setLoaded;
				texdata.needToUpdate = false;
				GetGLError(__FILE__,__LINE__);
			}
			debug debugLog.trace( "tex loaded to device: " ~ tex.id );
			tex.setLoaded;
			glBindTexture( tex.tType, 0 );
			tex.needToUpdate = false;
		}

//---------------------------------

		void unLoadTexture( CResTexture tex )
		in
		{
			moonAssert( tex !is null, "tex is null", __FILE__, __LINE__ );
		}
		body
		{
			if( tex.loaded )
			{
				glDeleteTextures( 1, &tex.textureID );
				tex.setNotLoaded;
			}
		}

}
